Skip to content

Changes parseLengthCodedNumber to parse big numbers #382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jan 31, 2013
Merged

Conversation

dresende
Copy link
Collaborator

The idea is to parse numbers over the JS precision using an external dependency and return the number as string.

@dresende
Copy link
Collaborator Author

Not yet to merge, just for people to comment and test locally. Documentation and tests are needed later.

@dresende dresende mentioned this pull request Jan 29, 2013
@kai-koch
Copy link
Collaborator

works with the example from #380

@kai-koch
Copy link
Collaborator

If you decide at the end, the value is already overflown, must be tested around line 162:

  } else if (byte === 254) {
    length = 8;
    if (this._buffer[this._offset + 7] === 1) {
         //HELP Big Number!
    }
  } else {

if (BigNumber) {
value = value.plus((new BigNumber(256)).pow(bytesRead).times(byte));
} else if (bytesRead == 7 && byte == 1) {
BigNumber = require("bignumber.js");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will make blocking FS calls, even if the module has been cached before. If you want to lazy-load this, you'll need to have a top-level variable that you use as an in-module cache.

edit: never mind, I see you're doing that. ignore me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, @felixge you were right. That variable is within the Parser.prototype.parseLengthCodedNumber function and should probably be moved out to the module level, otherwise require will be called each time a length coded number is parsed that is too large.

@kai-koch
Copy link
Collaborator

From line 156:

  var length, bytesRead;
  var BigNumber = false;
  var value = 0; 
  if (byte === 252) {
    length = 2;
  } else if (byte === 253) {
    length = 3;
  } else if (byte === 254) {
    length = 8;
    if (this._buffer[this._offset + 7] === 1) {
      BigNumber = require("bignumber.js");
      value = new BigNumber(0);
    }
  } else {
    throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + byte);
  }
  for (bytesRead = 0; bytesRead < length; bytesRead++) {
    byte = this._buffer[this._offset++];
    // overflow
    if (BigNumber) {
      value = value.plus((new BigNumber(256)).pow(bytesRead).times(byte));
    } else {
      value += Math.pow(256, bytesRead) * byte;
    }
  }

  return (BigNumber ? value.toString() : value);

Works with example from #380
Also works with small numbers.
Did not run the unit tests.

@dresende
Copy link
Collaborator Author

@kai-koch: I like your code, I just started the pull request before optimizing because I wanted to discuss the use of that dependency. I tested others and they all seemed bloated or faulty.

@felixge : you're saying the dependency should be on top of the file? I just load it if a big number is found, otherwise never require it.. and it should cache for future requires.. no?

@kai-koch
Copy link
Collaborator

I found something strange:
If one truncates the table and then sets the the AUTO_INCREMENT like

TRUNCATE t;
ALTER TABLE  `t` AUTO_INCREMENT =72057594037927935

the first run of my example returns an unprecise JavaScript number the second one the correct number.

D:\[...]\node\wutCollector>node issue380.js
New row ID: 72057594037927940

D:\[...]\node\wutCollector>node issue380.js
New row ID: 72057594037927936

In the database the entries start correctly at 72057594037927935
Same behaviour if I drop the table and create it with the original CREATE-Statemen from #380
Any idea why?
I am on WinXP using an apache friends xampp installation with phpMyAdmin as database frontend.

@dresende
Copy link
Collaborator Author

I'm having the same bug on OSX, I'm going to take a closer look.

@dresende
Copy link
Collaborator Author

I think that the very first overflow number does not have the byte = 1 on index 7, it has byte = 255 on index 0-6.

@kai-koch
Copy link
Collaborator

I think BigNumber = require("bignumber.js"); should be moved to the top of the module as var declaration.
It will be cached before script execution.
This could also come in handy, if you should decide to use bignumbers.js else where in node-mysql.

@dresende
Copy link
Collaborator Author

OK, I'm just finishing bug fix and I'll update asap.

@kai-koch
Copy link
Collaborator

My Windows calculator says this:

72057594037927935
  FF FF FF FF FF FF FF
  1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

72057594037927936
1 00 00 00 00 00 00 00
1 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

But I don't know how it is encode in the buffer node-mysql is using.

@dresende
Copy link
Collaborator Author

It's exactly that, but reversed (little endian). But we're still doing it wrong. The upper limit must be 2^53, which is: 9007199254740992.

@kai-koch
Copy link
Collaborator

Works for both numbers now. :-)

@dresende
Copy link
Collaborator Author

@felixge : any objections? :)

@kai-koch
Copy link
Collaborator

Question: Is that a signed or unsigned Big Int encoded in the Buffer?

EDIT: Duh, unsigned of course, since it is defined by the table defenition

@kai-koch
Copy link
Collaborator

We still got a problem:
9223372036854775808, stored in DB, but displays as 9223372036854776000 in the example script.

(9223372036854775807 is the max. value for signed 64-Interger)

@@ -154,30 +155,35 @@ Parser.prototype.parseLengthCodedNumber = function() {
}

var length;
var big_number = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the code would use bigNumber, not big_number.

@felixge
Copy link
Collaborator

felixge commented Jan 30, 2013

@felixge : any objections? :)

Well, I dislike switching the returned type from Number to String on >= 53 bit, this could certainly introduce subtle bugs in clients. That being said, I can't think of a better solution either (other than switching to a more enlightened language like Go, which I did recently : ).

Anyway, patch LGTM otherwise.

@dresende
Copy link
Collaborator Author

We could make an option to enable (or disable) this. Just need to know what is best by default.

@felixge
Copy link
Collaborator

felixge commented Jan 30, 2013

Just need to know what is best by default.

I would favor the current behavior as the default.

@sirian
Copy link

sirian commented Jan 30, 2013

yep. better to use current behavior as the default.
maybe enabling this option will also typecast bigint to string in select queries?
p.s. waiting fix)

@dresende
Copy link
Collaborator Author

Done some changes. What other behavior should be changed?

@felixge
Copy link
Collaborator

felixge commented Jan 30, 2013

maybe enabling this option will also typecast bigint to string in select queries?

Yes.

p.s. waiting fix)

?

@sirian
Copy link

sirian commented Jan 30, 2013

I'm waiting while it will be fixed in the npm repository
p.s. node-mysql-libmysqlclient also works incorrectly for bigint insert ids

…t cast to number if string value is over precision limit
@dresende
Copy link
Collaborator Author

For LONGLONG and other number columns, if supportBigNumbers option is enabled, won't be converted to Number if over precision limit.

@kai-koch
Copy link
Collaborator

I got the bignumber branch, it still has problems with numbers greater than '9223372036854775807'
line 170:

if (this._buffer[this._offset + 6].toString(2).length > 5 || this._buffer[this._offset + 7] == 1) {

Is wrong.
If I get the math right:

  • We have 8 Bytes that represent a 64-bit Number in LittleEndian (signed or unsigned? Does MySQL give this information?)
  • you then check:
    • if in byte with index 6 one of the 3 highest bits are set. Which would mean, if any is set we have a number with more than 53 bits
    • OR if bit 1 in in the byte with index 7 is set (bit 57 of the number).

This way you only detect big numbers that are smaller than 2^58.

So for unsigned 64-bit Integers line 156 must be:

if (this._buffer[this._offset + 6].toString(2).length > 5 || this._buffer[this._offset + 7]) {

Any bit set in byte with index 7 means BigNumber.

Which brings me to signed 64-Integers for these the line must look something like this:

if (this._buffer[this._offset + 6].toString(2).length > 5 ||
    (this._buffer[this._offset + 7] > 0 && this._buffer[this._offset + 7] < 128 ) ) {

Because:
if the highest bit in the byte with index 7 is set (bit 63) we have a negative number, which then again needs to be tested if it exceeds the 53 bits range of JS, depending on which bit-notation is given in the buffer by mysql for 64-bit signed .integers.

[edit: linenumber]

@kai-koch
Copy link
Collaborator

Another thing:
I think testing

this._buffer[this._offset + 6] > 31

is slightly faster than

this._buffer[this._offset + 6].toString(2).length > 5

@kai-koch
Copy link
Collaborator

While I am nagging, another suggestion since you are working on that part of the code anyway:
Change the name of variable 'byte' to 'bits'.
'bits' is no reserved word and makes clearer what the code does. :-P

@dresende
Copy link
Collaborator Author

I'm trusting on your code, I don't have time now to think about it :P I'm pulling the code now.

@kai-koch
Copy link
Collaborator

You better should have. ;)
The line for signed Integers breaks the current example code. :-(

Use the line for unsigend Integers, please:

if (this._buffer[this._offset + 6] > 31 || this._buffer[this._offset + 7]) {

@kai-koch
Copy link
Collaborator

This will work until someone works with signed 64-Bit greater than '9223372036854775807' or less than 0.

dresende added a commit that referenced this pull request Jan 31, 2013
Changes parseLengthCodedNumber to parse big numbers
@dresende dresende merged commit 913fcbc into master Jan 31, 2013
@dresende dresende deleted the bignumber branch January 31, 2013 00:33
dveeden pushed a commit to dveeden/mysql that referenced this pull request Jan 31, 2023
Add _binary prefix when interpolating []byte data.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants